"""
Copyright 2012 Wieser Dietmar, Margit Mutschlechner.

This file is part of Multimedia Social Networking Platform.

Multimedia Social Networking Platform is free software: 
you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Multimedia Social Networking Platform is distributed
in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Multimedia Social Networking Platform.  
If not, see <http://www.gnu.org/licenses/>.

"""

from threading import Thread
from msnp.service.network import NetworkController, BackupController, \
    NetworkLock, LeaveRequest, listening
from msnp.service.network.sending import Sender, ReliableTransportQueue
from msnp.service.network.networktimer import CrashTimer, ReliableTransportTimer
from msnp.logger import Logger
from msnp.service.network.logic import ResponsibilityLogic
from msnp.service.network.sending import ReliableNetworkExchange
from msnp.service.network.model.ReliableTransportModel import RTSearchSuccessorMessage
import time
from msnp.model import PTPModel
from msnp.gui import MySignal



def reconnectRing(newSuccessor, maxTries=3):
    """This function is executed during the reconnection of a broken ring.
    
    It is called from two points: a remote call and when the crashtimer finishes.
    It updates the predecessor of the successor and reacts depending on the return value.
    If the update was successful this peer sets its successor and sends its backup to the new successor.
    Otherwise this peer reconnects to the ring (sends a hello), waits for a re-hello, tries again to update the successor or handles a crash for the successsor.
    
    Args:
        newSuccessor: The new successor (PeerInfo instance).
        maxTries: This argument gives an upper bound for different tries in case of a failure.
    """

    myIdentity = NetworkController.getMySelf().getMyIdentity()
    #update successor
    returnType = Sender.updatePredecessorOfSuccessor(myIdentity, newSuccessor, True)
    if returnType == PTPModel.ReturnType.SUCCESSFUL:
        #set own successor and send him my backup
        NetworkController.getMySelf().setSuccessor(newSuccessor)
        BackupController.insertAtFinger(newSuccessor)
        Logger.info('ring closed -> new successor: ' + newSuccessor.getNameBundle())
    #I am excluded from the ring -> rehello
    elif returnType == PTPModel.ReturnType.RECONNECT:
        Logger.info("I have to rehello -> in Crashtimer because of RECONNECT")
        Sender.hello(True, None)
    elif returnType == PTPModel.ReturnType.FORCE_REHELLO:
        Logger.info("Waiting for rehello")
    elif returnType == PTPModel.ReturnType.UPDATE_PREDECESSOR_ERROR:
        if maxTries > 0:
            Logger.info("UPDATE_PREDECESSOR_ERROR while reconnecting ring to " + newSuccessor.getName())
            maxTries -= 1;
            reconnectRing(newSuccessor)
        else:
            Logger.info("UPDATE_PREDECESSOR_ERROR 3 tries -> handle crash for crashed identity: " + newSuccessor.getName())
            #in case of an error set the newSuccessor as new successor -> so i understand in the handlecrash procedure that i am the predecessor of crashed identity!
            NetworkController.getMySelf().setSuccessor(newSuccessor)
            handleCrash(newSuccessor)
    else:
        #in case of an error set the bestcrashbroadcastcallback as new successor -> so i understand in the handlecrash procedure (initiated in the updatepredecessor of successor in sender) that i am the predecessor of crashed identity!
        NetworkController.getMySelf().setSuccessor(newSuccessor)
        Logger.info("update predecessor of successor failed in allCrashCallbacksReceived. newSuccessor = " + newSuccessor.getName())
      
            
def handleCrash(crashedIdentity, alreadyReceivedUsers=None):
    """Called when the crash of a peer is recognized. Starts the handle crash thread.
    
    Args:
        crashedIdentity: The PeerInfo instance of the crashed peer.
        alreadyReceivedUsers: a list containing peers which did already recognize the crash.
    """
    
    if alreadyReceivedUsers == None:
        alreadyReceivedUsers = []
    handleCrashThread = HandleCrashThread(crashedIdentity, alreadyReceivedUsers)
    handleCrashThread.start()    


##########################
## execute remote calls ##
##########################

def rtMessage(rtMsg, originalIdentity, alreadyReceivedUsers):
    """This function forwardes the call to the ReliableNetworkExchange module.
    
    Args:
        rtMsg: The ReliableTransportMessage instance.
        originalIdentity: The PeerInfo instance that initiated the reliable transport message.
        alreadyReceivedUsers: A list of peers, that received already this reliable transport message.
    """
    
    ReliableNetworkExchange.processRTMessage(rtMsg, originalIdentity, alreadyReceivedUsers)


def rtMessageErrorCallback(rtMsg):
    """This function initiates a broadcast.
    
    This happens if a reliable transport message cannot be forwarded sequentially through the ring.
    
    Args:
        rtMsg: The ReliableTransportMessage instance.
    """
    
    Logger.info("Got RTMessageErrorCallback")
    #disable the running timer
    ReliableTransportTimer.disableRTTimerEntry(rtMsg)
    # delete entry from queue because we want to process the message
    ReliableTransportQueue.deleteRTEntry(rtMsg)
    #in case of a search successor message start the crashtimer before the broadcast
    if isinstance(rtMsg, RTSearchSuccessorMessage):
        crashedIdentity = rtMsg.getCrashedIdentity()
        CrashTimer.startCallbacksReceivedTimer(crashedIdentity)
    Logger.info("  -> Messaget with type '" + rtMsg.getType() + "' and compareidentity " + rtMsg.getCompareIdentity().getName() + " needs a broadcast.")
    rtMsg.setUseBroadcast(True)
    ReliableNetworkExchange.processRTMessage(rtMsg, None, None)
    #sleep because the broadcast is executed in a thread so it could happen that i leave before the broadcast is forwarded
    time.sleep(2)
    LeaveRequest.release()


def doReconnectRing(newSuccessor, rtMessage):
    """This function reconnects a broken ring.
    
    Args:
        newSuccessor: The new successor (PeerInfo instance).
        rtMessage: The ReliableTransportMessage instance.
    """
    
    Logger.info("Got ReconnectRingCallback")
    Logger.info("-------> RT-Message with type 'search_successor' and new successor " + newSuccessor.getName() + " came through the ring without using broadcast")
    ReliableTransportTimer.disableRTTimerEntry(rtMessage)
    NetworkLock.acquireCrashFinishLock()
    try:
        reconnectRing(newSuccessor)
        LeaveRequest.release()
    finally:
        NetworkLock.releaseCrashFinishLock()


def callbackCrashBroadcast(crashedIdentity, answerIdentity, successorOfCrashPeerFound):
    """This function forwards the call to the CrashTimer module.
    
    Args:
        crashedIdentity: The PeerInfo instance of the crashed peer.
        answerIdentity: A PeerInfo instance.
        successorOfCrashPeerFound: A boolean determining whether the successor of the crashed peer is already found.
    """
    
    CrashTimer.newCrashBroadcastCallback(crashedIdentity, answerIdentity, successorOfCrashPeerFound)


def doHandleCrash(crashedIdentity, alreadyReceivedUsers):
    """Searches the predecessor of the crashed identity and starts a crash-message.
    
    First it checks whether a broadcast for that peer is already sent.
    If not it checks whether the crashed peer is between this peer and its successor.
    If yes it checks with a isAlive if the peer is really crashed, then initializes a sequential search for the new successor.
    If not it forwards the search for the predecessor of the crashed peer.
    
    Args:
        crashedIdentity: The PeerInfo instance of the crashed peer.
        alreadyReceivedUsers: A list of peers that already received the message.
    """
    
    try:
        Logger.info("execute handle crash for " + crashedIdentity.getName())
        myself = NetworkController.getMySelf()
        predecessor = myself.getPredecessor()
        successor = myself.getSuccessor()
        #only process crash if I didn't already sent or forwarded a broadcast for it
        if crashedIdentity != None:
            NetworkLock.acquireRTLock()
            alreadyProcessed = False
            try:
                alreadyProcessed = ReliableNetworkExchange.alreadyProcessedCrashMessage(crashedIdentity.getName())
            finally:
                NetworkLock.releaseRTLock()
            if alreadyProcessed == False:
                #if there were more than 2 peers in the ring
                if predecessor != None and successor != None and predecessor.getHashedNameLong() != successor.getHashedNameLong():
                    myIdentity = myself.getMyIdentity()
                    #if the crashed identity is between me and my successor (successor is responsible) I start a crash-broadcast to find my new successor
                    if ResponsibilityLogic.isResponsible(myIdentity.getHashedNameLong(), crashedIdentity.getHashedNameLong(), successor.getHashedNameLong()):
                        if Sender.isAlive(crashedIdentity) == False:
                            #start broadcast to search for my new successor
                            Logger.info('predecessor of crashed identity found')
                            message = RTSearchSuccessorMessage(crashedIdentity, myIdentity)
                            ReliableNetworkExchange.initiateRTMessage(message)
                        else:
                            Logger.info("Handle Crash: peer " + crashedIdentity.getName() + " is alive!!")
                    elif alreadyReceivedUsers.__contains__(myIdentity.getName()) == False: 
                        alreadyReceivedUsers.append(myIdentity.getName())
                        Logger.info('find predecessor of crashed identity')
                        Sender.findPredecessorOfCrashedIdentity(crashedIdentity, alreadyReceivedUsers)
                    else:
                        Logger.info("handle crash failed -> i'm already contained in alreadyReceivedUsers")
                #if there were only 2 peers in the ring and the other one crashed i am the only peer
                else:
                    #is the identity really crashed
                    if Sender.isAlive(predecessor) == False:
                        NetworkLock.acquireFingerLock()
                        try:
                            Logger.info("handlecrash: predecessor and successor are the same!!! reset my fingers")
                            myself.initFingers()
                            myself.setPredecessor(None)
                            #now i am responsible for the crashed identity
                            BackupController.setBackupFromToNone(crashedIdentity.getName())
                        finally:
                            NetworkLock.releaseFingerLock()
            else:
                Logger.info("Handle Crash: crash for peer " + crashedIdentity.getName() + " already processed")
        else:
            Logger.info("crashedIdentity is None")
    except:
        Logger.info("Error while handling crash for peer " + crashedIdentity.getName())  
         
##############################
## threads for remote calls ##
##############################

class RTMessageThread(Thread):
    """Thread to send a reliable transport message.

    Attributes:
        __rtMsg: The ReliableTransportMessage instance.
        __originalIdentity: The initiator of the message (PeerInfo instance)
        __alreadyReceivedUsers: A list of peers that already received the message.
    """
    
    def __init__(self, rtMsg, originalIdentity, alreadyReceivedUsers):
        """the init method"""
        Thread.__init__(self)
        self.__rtMsg = rtMsg
        self.__originalIdentity = originalIdentity
        self.__alreadyReceivedUsers = alreadyReceivedUsers

    def run(self):
        """This method forwards the call to the rtMessage function."""
        rtMessage(self.__rtMsg, self.__originalIdentity, self.__alreadyReceivedUsers)
        
        
class RTMessageErrorCallbackThread(Thread):
    """Thread to process the error callback during the sequentially delivery of a reliable transport message.

    Attributes:
        __rtMsg: The ReliableTransportMessage instance.
    """
    
    def __init__(self, rtMsg):
        """the init method"""
        Thread.__init__(self)
        self.__rtMsg = rtMsg

    def run(self):
        """This method forwards the call to the rtMessageErrorCallback function."""
        rtMessageErrorCallback(self.__rtMsg)
        
        
class ReconnectRingThread(Thread):
    """Thread to reconnect a broken ring.

    Attributes:
        __newSuccessor: The new successor (PeerInfo instance).
        __rtMessage: The ReliableTransportMessage instance. 
    """
    
    def __init__(self, newSuccessor, rtMessage):
        """the init method"""
        Thread.__init__(self)
        self.__newSuccessor = newSuccessor
        self.__rtMessage = rtMessage

    def run(self):
        """This method forwards the call to the doReconnectRing function."""
        doReconnectRing(self.__newSuccessor, self.__rtMessage)
      
      
class CallbackCrashBroadcastThread(Thread):
    """Thread for executing the crash broadcast callback.

    Attributes:
        __crashedIdentity: The PeerInfo instance of the crashed peer.
        __answerIdentity: A PeerInfo instance.
        __successorOfCrashPeerFound: A boolean determining whether the successor of the crashed peer is already found.
    """
    
    def __init__(self, crashedIdentity, answerIdentity, successorOfCrashPeerFound):
        """the init method"""
        Thread.__init__(self)
        self.__crashedIdentity = crashedIdentity
        self.__answerIdentity = answerIdentity
        self.__successorOfCrashPeerFound = successorOfCrashPeerFound

    def run(self):
        """This method forwards the call to the callbackCrashBroadcast function."""
        callbackCrashBroadcast(self.__crashedIdentity, self.__answerIdentity, self.__successorOfCrashPeerFound)
        
        
class HandleCrashThread(Thread):
    """Thread for executing the handle crash procedure.

    Attributes:
        __crashedIdentity: The PeerInfo instance of the crashed peer.
        __alreadyReceivedUsers: a list containing peers which did already recognize the crash.
    """
    
    def __init__(self, crashedIdentity, alreadyReceivedUsers):
        """the init method"""
        Thread.__init__(self)
        self.__crashedIdentity = crashedIdentity
        self.__alreadyReceivedUsers = alreadyReceivedUsers
        
    def run(self):
        """this method stops all daemons and informs the graphical user interface about the crash."""
        listening.Listener.ListeningThread.stopListening()
        MySignal.emitConnectionLostSignal()


#####################
## execute threads ##
#####################

def executeReconnectRing(newSuccessor, rtMessage):
    """This function starts a thread for reconnecting the ring.
    
    Args:
        newSuccessor: The new successor (PeerInfo instance).
        rtMessage: The ReliableTransportMessage instance. 
    """
    
    rThread = ReconnectRingThread(newSuccessor, rtMessage)
    rThread.start()
    
    
def executeRTMessageErrorCallback(rtMessage):
    """This function starts the Thread to process the error callback during the sequentially delivery of a reliable transport message.

    Args:
        rtMessage: The ReliableTransportMessage instance.
    """
    
    rtThread = RTMessageErrorCallbackThread(rtMessage)
    rtThread.start()
    
    
def executeRTMessage(rtMsg, originalIdentity=None, alreadyReceivedUsers=None):
    """This function starts a Thread to send a reliable transport message.

    Args:
        rtMsg: The ReliableTransportMessage instance.
        originalIdentity: The initiator of the message (PeerInfo instance)
        alreadyReceivedUsers: A list of peers that already received the message.
    """
    
    rtThread = RTMessageThread(rtMsg, originalIdentity, alreadyReceivedUsers)
    rtThread.start()
    
    
def executeCallbackCrashBroadcast(crashedIdentity, answerIdentity, successorOfCrashPeerFound):
    """This function starts a Thread for executing the crash broadcast callback.

    Args:
        crashedIdentity: The PeerInfo instance of the crashed peer.
        answerIdentity: A PeerInfo instance.
        successorOfCrashPeerFound: A boolean determining whether the successor of the crashed peer is already found.
    """
    
    callbackCrashThread = CallbackCrashBroadcastThread(crashedIdentity, answerIdentity, successorOfCrashPeerFound)
    callbackCrashThread.start()
